from base64 import b64encode, b64decode
from StringIO import StringIO
import hashlib
import random
import sys
import struct

# Oh wow, apparently my save-games are going to be HUGE.
# This is what we have terabyte-sized disks for, I guess.

SerializationVersion = (2,0)

SupportedClasses = {}

def supportsSerialization( classObject ):
	SupportedClasses[ classObject.__name__ ] = classObject

class UnsupportedObject:
	def __init__(self, s):
		self.s  = s
	def __str__(self):
		return self.s

class NothingValue: pass

class MalformedFile: pass
class IncompatibleVersion: pass

class DummyClass: pass

class Serializator:
	def __init__(self, classList, pointers = {}):
		self.pointers = pointers
		#for classOrFunction in pointers:
		#	self.pointers[classOrFunction.__name__] = classOrFunction
		self.pointersInverse = {}
		for name, pointer in self.pointers.items():
			assert not self.pointersInverse.has_key( pointer )
			self.pointersInverse[ pointer ] = name
		self.supportedClasses = {}
		for classObject in classList:
			self.supportedClasses[ classObject.__name__ ] = classObject
		self.restoredObjects = []
		self.memo = {} # object -> id when writing, id -> object when reading
		self.encoders = {} # type -> method
		self.decoders = {} # identifier -> method
		self.encoders[ int ] = self.encodeInteger
		self.encoders[ long ] = self.encodeInteger
		self.decoders[ "int" ] = self.decodeInteger
		self.encoders[ float ] = self.encodeFloat
		self.decoders[ "float" ] = self.decodeFloat
		self.encoders[ str ] = self.encodeString
		self.decoders[ "string" ] = self.decodeString
		self.encoders[ list ] = self.encodeList
		self.decoders[ "list" ] = self.decodeList
		self.encoders[ tuple ] = self.encodeTuple
		self.decoders[ "tuple" ] = self.decodeTuple
		self.encoders[ dict ] = self.encodeDict
		self.decoders[ "dict" ] = self.decodeDict
		self.encoders[ type(None) ] = self.encodeNone
		self.decoders[ "none" ] = self.decodeNone
		self.encoders[ bool ] = self.encodeBool
		self.decoders[ "bool" ] = self.decodeBool
	def encodeInteger(self, value ): return ( "int", str(value) )
	def decodeInteger(self, data ): return int( data[1] )
	def encodeFloat(self, value ): return ( "float", str(value) )
	def decodeFloat(self, data ): return float( data[1] )
	def encodeString(self, value ): return ( "string", value )
	def decodeString(self, data ): return str( data[1] )
	def encodeNone(self, value ): return ( "none", )
	def decodeNone(self, data ): return None
	def encodeBool(self, value ):
		if value: return ( "bool", "1" )
		return ( "bool", "0" )
	def decodeBool(self, data ):
		return data[1] != "0"
	def encodeList(self, value):
		flo = StringIO()
		self.writeValue( flo, len( value) )
		for item in value:
			self.writeValue( flo, item )
		return ( "list", flo.getvalue() )
	def decodeList(self, data ):
		flo = StringIO( data[1] )
		rv = []
		for i in range( self.readValue( flo ) ):
			rv.append( self.readValue( flo ) )
		return rv
	def encodeTuple(self, value):
		strList, data = self.encodeList( value )
		return ( "tuple", data )
	def decodeTuple(self, data ):
		return tuple( self.decodeList( data ) )
	def encodeDict(self, value):
		flo = StringIO()
		self.writeValue( flo, len( value) )
		for key, value in value.items():
			self.writeValue( flo, key )
			self.writeValue( flo, value )
		return ( "dict", flo.getvalue() )
	def decodeDict(self, data):
		flo = StringIO( data[1] )
		rv = {}
		for i in range( self.readValue( flo ) ):
			key = self.readValue( flo )
			value = self.readValue( flo )
			rv[key] = value
		return rv
	def writeData(self, flo, datalist):
		flo.write( self.encodeData( datalist ) )
	def readData(self, flo):
		noElts = struct.unpack( "<I", flo.read( 4 ) )[ 0 ]
		rv = []
		for j in range(noElts):
			strSize = struct.unpack( "<I", flo.read( 4 ) )[0]
			rv.append( flo.read( strSize ) )
		return rv
	def encodeData(self, datalist):
		rv = []
		rv.append( struct.pack( "<I", len(datalist) ) )
		for item in datalist:
			rv.append( struct.pack( "<I", len(item) ) )
			rv.append( item )
		return "".join( rv )
	def decodeData(self, data ):
		flo = StringIO( data )
		return self.readData( flo )
	def writeObject(self, flo, value):
		for name in value.__class__.serializedAttributes:
			try:
				attribute = getattr( value, name )
				self.writeValue( flo, attribute )
			except AttributeError:
				self.writeData( flo, [ "nothing" ] )
	def readObject(self, flo, classObject, target):
		for name in classObject.serializedAttributes:
			try:
				data = self.readValue( flo )
				setattr( target, name, data )
			except NothingValue:
				pass
	def writeValue(self, flo, value):
		if self.memo.has_key( id(value) ):
			self.writeData( flo, ["ref", str( id(value) ) ] )
		else:
			self.writeData( flo, ["id", str( id(value) ) ] )
			self.memo[ id(value) ] = value
			valueClass = getattr( value, "__class__", None )
			try:
				isPointer = self.pointersInverse.has_key(value)
			except TypeError: # something unhashable
				isPointer = False
			if isPointer:
				self.writeData( flo, [ "pointer", self.pointersInverse[value] ] )
			elif valueClass in self.supportedClasses.values():
				self.writeData( flo, ["object", valueClass.__name__] )
				self.writeObject( flo, value )
			else:
				try:
					data = self.encoders[ type(value) ]( value )
					self.writeData( flo, data )
				except KeyError:
					print >>sys.stderr, value, valueClass
					raise UnsupportedObject( str(type(value)) )
	def readValueCore(self, flo, idNo):
		data = self.readData( flo )
		typeData = data[0]
		if typeData == "object":
			className = data[1]
			classObject = self.supportedClasses[ className ]
			obj = self.memo[idNo] = DummyClass()
			obj.__class__ = classObject
				# Apparently this works...?
				# Caveat: this requires that we use only
				#  old-style classes, or, if we use
				#  object() instead of DummyClass(),
				#  only new-style classes.
			self.restoredObjects.append( obj )
			return self.readObject( flo, classObject, obj )
		elif typeData == "pointer": # class or function pointers
			ptrName = data[1]
			value = self.pointers[ ptrName ]
			self.memo[idNo] = value
			return value
		else:
			try:
				value = self.decoders[ typeData ]( data )
				self.memo[idNo] = value
				return value
			except KeyError:
				raise UnsupportedObject( typeData )
	def readValue(self, flo):
		data = self.readData( flo )
		typeData = data[0]
		if typeData == "id":
			objId = int( data[1] )
			self.readValueCore( flo, objId )
			return self.memo[ objId ]
		elif typeData == "ref":
			refNo = int( data[1] )
			return self.memo[ refNo ]
		elif typeData == "nothing":
			raise NothingValue()
		else:
			raise MalformedFile()
	def structureHash(self):
		separator = "X" * 8
		hash = hashlib.md5()
		for className, classObject in self.supportedClasses.items():
			hash.update( className )
			hash.update( separator )
			for serializedAttribute in classObject.serializedAttributes:
				hash.update( serializedAttribute )
				hash.update( separator )
		return hash.hexdigest()
	def fullVersion(self, version):
		return "%s-%s-%s" % ( str(version), str(SerializationVersion), self.structureHash() )
	def write(self, flo, version, target):
		self.writeData( flo, ( "version", self.fullVersion(version) ) )
		self.writeValue( flo, target )
	def read(self, flo, version):
		strVersion, versionInfo = self.readData( flo )
		if strVersion != "version": raise MalformedFile()
		if versionInfo != self.fullVersion( version ): raise IncompatibleVersion()
		rv = self.readValue( flo )
		for obj in self.restoredObjects:
			restore = getattr( obj, "restore", None )
			if callable( restore ):
				restore()
		return rv
	def writeFile(self, filename, version, target):
		flo = open( filename, "w" )
		self.write( flo, version, target )
		flo.close()
	def readFile(self, filename, version):
		flo = open( filename, "r" )
		rv = self.read( flo, version )
		flo.close()
		return rv
